﻿//StorageRegistry.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections.Generic;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Algo.Candles;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Базовый класс, описывающий хранилище маркет-данных.
	/// </summary>
	public class StorageRegistry : IStorageRegistry
	{
		private sealed class TradeStorage : MarketDataStorage<Trade, long, TradeMetaInfo>
		{
			public TradeStorage(Security security, IMarketDataDrive drive)
				: base(security, null, trade => trade.Time, trade => trade.Security, trade => trade.Id, new TradeSerializer(security), drive)
			{
				Version = MarketDataVersions.Version43;
			}

			protected override IEnumerable<Trade> FilterNewData(IEnumerable<Trade> data, TradeMetaInfo metaInfo)
			{
				var prevId = metaInfo.PrevId;
				var prevTime = metaInfo.LastTime;

				return data.Where(t =>
				{
					if (t.Time > prevTime)
						return true;
					else if (t.Time == prevTime)
						return t.Id != prevId; // если разные сделки имеют одинаковое время
					else
						return false;
				});
			}
		}

		private sealed class MarketDepthStorage : MarketDataStorage<MarketDepth, DateTime, MarketDepthMetaInfo>
		{
			public MarketDepthStorage(Security security, IMarketDataDrive drive)
				: base(security, null, depth => depth.LastChangeTime, depth => depth.Security, depth => depth.LastChangeTime.Truncate(), new MarketDepthSerializer(security), drive)
			{
			}
		}

		private sealed class OrderLogStorage : MarketDataStorage<OrderLogItem, long, OrderLogMetaInfo>
		{
			public OrderLogStorage(Security security, IMarketDataDrive drive)
				: base(security, null, item => item.Order.Time, item => item.Order.Security, item => item.Order.TransactionId, new OrderLogSerializer(security), drive)
			{
				Version = MarketDataVersions.Version43;
			}

			protected override IEnumerable<OrderLogItem> FilterNewData(IEnumerable<OrderLogItem> data, OrderLogMetaInfo metaInfo)
			{
				if (metaInfo.Version < MarketDataVersions.Version34)
					return base.FilterNewData(data, metaInfo);

				var prevTransId = metaInfo.LastTransactionId;
				return data.Where(i => i.Order.TransactionId > prevTransId);
			}

			protected override bool NeedUpgrade(OrderLogMetaInfo metaInfo)
			{
				return false;
			}
		}

		private sealed class OrderTraceStorage : MarketDataStorage<OrderTraceItem, DateTime, OrderTraceMetaInfo>
		{
			public OrderTraceStorage(Security security, IMarketDataDrive drive)
				: base(security, null, item => item.Time, item => item.Order.Security, item => item.Time.Truncate(), new OrderTraceSerializer(security), drive)
			{
			}
		}

		private sealed class CandleStorage<TCandle> : MarketDataStorage<TCandle, DateTime, CandleMetaInfo>, IMarketDataStorage<Candle>, IMarketDataStorageInfo<Candle>
			where TCandle : Candle, new()
		{
			public CandleStorage(Security security, object arg, IMarketDataDrive drive)
				: base(security, arg, candle => candle.OpenTime, candle => candle.Security, candle => candle.OpenTime.Truncate(), new CandleSerializer<TCandle>(security, arg), drive)
			{
				GetTime = candle => candle.OpenTime;
				Version = MarketDataVersions.Version41;
			}

			void IMarketDataStorage<Candle>.Save(IEnumerable<Candle> data)
			{
				Save(data.Cast<TCandle>());
			}

			void IMarketDataStorage<Candle>.Delete(IEnumerable<Candle> data)
			{
				Delete(data.Cast<TCandle>());
			}

			IEnumerableEx<Candle> IMarketDataStorage<Candle>.Load(DateTime date)
			{
				var candles = Load(date);
				return candles.Cast<Candle>().ToEx(candles.Count);
			}

			public Func<Candle, DateTime> GetTime { get; private set; }
		}

		private sealed class SecurityChangeStorage : MarketDataStorage<SecurityChange, DateTime, SecurityMetaInfo>
		{
			public SecurityChangeStorage(Security security, IMarketDataDrive drive)
				: base(security, null, value => value.Time, value => value.Security, value => value.Time.Truncate(), new SecurityChangeSerializer(security), drive)
			{
				Version = MarketDataVersions.Version41;
			}
		}

		private readonly SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<Trade>> _tradeStorages = new SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<Trade>>();
		private readonly SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<MarketDepth>> _depthStorages = new SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<MarketDepth>>();
		private readonly SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<OrderLogItem>> _logStorages = new SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<OrderLogItem>>();
		private readonly SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<SecurityChange>> _securityChangeStorages = new SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<SecurityChange>>();
		private readonly SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<OrderTraceItem>> _orderTraceStorages = new SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<OrderTraceItem>>();
		private readonly SynchronizedDictionary<Tuple<Security, IMarketDataDrive, Type, object>, IMarketDataStorage<Candle>> _candleStorages = new SynchronizedDictionary<Tuple<Security, IMarketDataDrive, Type, object>, IMarketDataStorage<Candle>>();

		/// <summary>
		/// Создать <see cref="StorageRegistry"/>.
		/// </summary>
		public StorageRegistry()
		{
		}

		private readonly Lazy<IMarketDataDrive> _defaultDrive = new Lazy<IMarketDataDrive>(() => new LocalMarketDataDrive());

		/// <summary>
		/// Хранилище, которое используется по-умолчанию.
		/// </summary>
		public virtual IMarketDataDrive DefaultDrive
		{
			get { return _defaultDrive.Value; }
		}

		/// <summary>
		/// Добавить хранилище тиковых сделок.
		/// </summary>
		/// <param name="storage">Хранилище тиковых сделок</param>
		public void RegisterTradeStorage(IMarketDataStorage<Trade> storage)
		{
			RegisterStorage(_tradeStorages, storage);
		}

		/// <summary>
		/// Добавить хранилище стаканов.
		/// </summary>
		/// <param name="storage">Хранилище стаканов.</param>
		public void RegisterMarketDepthStorage(IMarketDataStorage<MarketDepth> storage)
		{
			RegisterStorage(_depthStorages, storage);
		}

		/// <summary>
		/// Зарегистрировать хранилище лога заявок.
		/// </summary>
		/// <param name="storage">Хранилище лога заявок.</param>
		public void RegisterOrderLogStorage(IMarketDataStorage<OrderLogItem> storage)
		{
			RegisterStorage(_logStorages, storage);
		}

		/// <summary>
		/// Зарегистрировать хранилище изменений заявок.
		/// </summary>
		/// <param name="storage">Хранилище изменений заявок.</param>
		public void RegisterOrderTraceStorage(IMarketDataStorage<OrderTraceItem> storage)
		{
			RegisterStorage(_orderTraceStorages, storage);
		}

		/// <summary>
		/// Зарегистрировать хранилище изменений инструмента.
		/// </summary>
		/// <param name="storage">Хранилище изменений инструмента.</param>
		public void RegisterSecurityChangeStorage(IMarketDataStorage<SecurityChange> storage)
		{
			RegisterStorage(_securityChangeStorages, storage);
		}

		private static void RegisterStorage<T>(SynchronizedDictionary<Tuple<Security, IMarketDataDrive>, IMarketDataStorage<T>> storages, IMarketDataStorage<T> storage)
		{
			if (storages == null)
				throw new ArgumentNullException("storages");

			if (storage == null)
				throw new ArgumentNullException("storage");

			storages.Add(new Tuple<Security, IMarketDataDrive>(storage.Security, storage.Drive), storage);
		}

		/// <summary>
		/// Добавить хранилище свечек.
		/// </summary>
		/// <param name="storage">Хранилище свечек.</param>
		/// <param name="candleType">Тип свечки.</param>
		/// <param name="arg">Параметр свечки.</param>
		public void RegisterCandleStorage(IMarketDataStorage<Candle> storage, Type candleType, object arg)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");

			if (candleType == null)
				throw new ArgumentNullException("candleType");

			if (arg == null)
				throw new ArgumentNullException("arg");

			_candleStorages.Add(new Tuple<Security, IMarketDataDrive, Type, object>(storage.Security, storage.Drive, candleType, arg), storage);
		}

		/// <summary>
		/// Получить хранилище тиковых сделок для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Хранилище тиковых сделок.</returns>
		public IMarketDataStorage<Trade> GetTradeStorage(Security security)
		{
			return GetTradeStorage(security, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище тиковых сделок для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="drive">Хранилище (база данных, файл и т.д.).</param>
		/// <returns>Хранилище тиковых сделок.</returns>
		public IMarketDataStorage<Trade> GetTradeStorage(Security security, IMarketDataDrive drive)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (drive == null)
				throw new ArgumentNullException("drive");

			return _tradeStorages.SafeAdd(new Tuple<Security, IMarketDataDrive>(security, drive), key =>
			{
				if (key.Item1 is ContinuousSecurity)
					return new ContinuousSecurityMarketDataStorage<Trade>((ContinuousSecurity)key.Item1, null, t => t.Time, t => t.Security, GetTradeStorage, drive);
				else if (key.Item1 is IndexSecurity)
					return new IndexSecurityMarketDataStorage<Trade>((IndexSecurity)key.Item1, null, d => d.Security, GetTradeStorage, drive);
				else
					return new TradeStorage(key.Item1, key.Item2);
			});
		}

		/// <summary>
		/// Получить хранилище стаканов для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Хранилище стаканов.</returns>
		public IMarketDataStorage<MarketDepth> GetMarketDepthStorage(Security security)
		{
			return GetMarketDepthStorage(security, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище стаканов для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="drive">Хранилище (база данных, файл и т.д.).</param>
		/// <returns>Хранилище стаканов.</returns>
		public IMarketDataStorage<MarketDepth> GetMarketDepthStorage(Security security, IMarketDataDrive drive)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (drive == null)
				throw new ArgumentNullException("drive");

			return _depthStorages.SafeAdd(new Tuple<Security, IMarketDataDrive>(security, drive), key =>
			{
				if (key.Item1 is ContinuousSecurity)
					return new ContinuousSecurityMarketDataStorage<MarketDepth>((ContinuousSecurity)key.Item1, null, md => md.LastChangeTime, md => md.Security, GetMarketDepthStorage, drive);
				else if (key.Item1 is IndexSecurity)
					return new IndexSecurityMarketDataStorage<MarketDepth>((IndexSecurity)key.Item1, null, d => d.Security, GetMarketDepthStorage, drive);
				else
					return new MarketDepthStorage(key.Item1, key.Item2);
			});
		}

		/// <summary>
		/// Получить хранилище лога заявок для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Хранилище лога заявок.</returns>
		public IMarketDataStorage<OrderLogItem> GetOrderLogStorage(Security security)
		{
			return GetOrderLogStorage(security, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище лога заявок для заданного инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="drive">Хранилище (база данных, файл и т.д.).</param>
		/// <returns>Хранилище лога заявок.</returns>
		public IMarketDataStorage<OrderLogItem> GetOrderLogStorage(Security security, IMarketDataDrive drive)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (drive == null)
				throw new ArgumentNullException("drive");

			return _logStorages.SafeAdd(new Tuple<Security, IMarketDataDrive>(security, drive), key => new OrderLogStorage(key.Item1, key.Item2));
		}

		/// <summary>
		/// Получить хранилище изменений инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Хранилище изменений инструмента.</returns>
		public IMarketDataStorage<SecurityChange> GetSecurityChangeStorage(Security security)
		{
			return GetSecurityChangeStorage(security, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище лога изменений инструмента.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="drive">Хранилище (база данных, файл и т.д.).</param>
		/// <returns>Хранилище изменений инструмента.</returns>
		public IMarketDataStorage<SecurityChange> GetSecurityChangeStorage(Security security, IMarketDataDrive drive)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (drive == null)
				throw new ArgumentNullException("drive");

			return _securityChangeStorages.SafeAdd(new Tuple<Security, IMarketDataDrive>(security, drive), key => new SecurityChangeStorage(key.Item1, key.Item2));
		}

		/// <summary>
		/// Получить хранилище изменений заявок.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Хранилище изменений заявок.</returns>
		public IMarketDataStorage<OrderTraceItem> GetOrderTraceStorage(Security security)
		{
			return GetOrderTraceStorage(security, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище лога изменений заявок.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="drive">Хранилище (база данных, файл и т.д.).</param>
		/// <returns>Хранилище изменений заявок.</returns>
		public IMarketDataStorage<OrderTraceItem> GetOrderTraceStorage(Security security, IMarketDataDrive drive)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (drive == null)
				throw new ArgumentNullException("drive");

			return _orderTraceStorages.SafeAdd(new Tuple<Security, IMarketDataDrive>(security, drive), key => new OrderTraceStorage(key.Item1, key.Item2));
		}

		/// <summary>
		/// Получить хранилище свечек для заданного инструмента.
		/// </summary>
		/// <param name="candleType">Тип свечки.</param>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <returns>Хранилище свечек.</returns>
		public IMarketDataStorage<Candle> GetCandleStorage(Type candleType, Security security, object arg)
		{
			return GetCandleStorage(candleType, security, arg, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище свечек для заданного инструмента.
		/// </summary>
		/// <param name="candleType">Тип свечки.</param>
		/// <param name="security">Инструмент.</param>
		/// <param name="arg">Параметр свечки.</param>
		/// <param name="drive">Хранилище (база данных, файл и т.д.).</param>
		/// <returns>Хранилище свечек.</returns>
		public IMarketDataStorage<Candle> GetCandleStorage(Type candleType, Security security, object arg, IMarketDataDrive drive)
		{
			if (candleType == null)
				throw new ArgumentNullException("candleType");

			if (security == null)
				throw new ArgumentNullException("security");

			if (arg == null)
				throw new ArgumentNullException("arg");

			if (drive == null)
				throw new ArgumentNullException("drive");

			return _candleStorages.SafeAdd(new Tuple<Security, IMarketDataDrive, Type, object>(security, drive, candleType, arg), key =>
			{
				if (security is ContinuousSecurity)
					return new ContinuousSecurityMarketDataStorage<Candle>((ContinuousSecurity)security, arg, c => c.OpenTime, c => c.Security, s => GetCandleStorage(candleType, s, arg, drive), drive);
				else if (security is IndexSecurity)
					return new IndexSecurityMarketDataStorage<Candle>((IndexSecurity)security, arg, c => c.Security, s => GetCandleStorage(candleType, s, arg, drive), drive);
				else
					return typeof(CandleStorage<>).Make(candleType).CreateInstance<IMarketDataStorage<Candle>>(security, arg, drive);
			});
		}

		/// <summary>
		/// Получить хранилище маркет-данных.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="dataType">Тип маркет-данных.</param>
		/// <param name="arg">Параметр, ассоциированный с типом <paramref name="dataType"/>. Например, <see cref="Candle.Arg"/>.</param>
		/// <returns>Хранилище маркет-данных.</returns>
		public IMarketDataStorage GetStorage(Security security, Type dataType, object arg)
		{
			return GetStorage(security, dataType, arg, DefaultDrive);
		}

		/// <summary>
		/// Получить хранилище маркет-данных.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <param name="dataType">Тип маркет-данных.</param>
		/// <param name="arg">Параметр, ассоциированный с типом <paramref name="dataType"/>. Например, <see cref="Candle.Arg"/>.</param>
		/// <param name="drive">Хранилище.</param>
		/// <returns>Хранилище маркет-данных.</returns>
		public IMarketDataStorage GetStorage(Security security, Type dataType, object arg, IMarketDataDrive drive)
		{
			if (dataType == null)
				throw new ArgumentNullException("dataType");

			if (dataType == typeof(Trade))
				return GetTradeStorage(security, drive);
			else if (dataType == typeof(MarketDepth))
				return GetMarketDepthStorage(security, drive);
			else if (dataType == typeof(SecurityChange))
				return GetSecurityChangeStorage(security, drive);
			else if (dataType == typeof(OrderLogItem))
				return GetOrderLogStorage(security, drive);
			else if (typeof(Candle).IsAssignableFrom(dataType))
				return GetCandleStorage(dataType, security, arg, drive);
			else
				throw new ArgumentOutOfRangeException("dataType", dataType, "Неизвестный тип маркет-данных.");
		}
	}
}